package com.ruoyi.handler.socket;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.HexUtil;
import com.ruoyi.common.constant.CommonContants;
import com.ruoyi.common.core.domain.entity.socket.bean.AuthRespMsg;
import com.ruoyi.common.core.domain.entity.socket.bean.CommonRespMsg;
import com.ruoyi.common.core.domain.entity.socket.bean.Message;
import com.ruoyi.common.core.domain.entity.socket.bean.RegisterRespMsg;
import com.ruoyi.common.core.domain.entity.system.*;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MyByteUtil;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.service.IBoatMachineWorkingInfoService;
import com.ruoyi.system.service.IHeartbeatMsgService;
import com.ruoyi.system.service.IMeterInfoService;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Slf4j
public class SocketMessageHandler1 {


    /**
     * 读取设备注册消息。
     *
     * @param in
     * @param messageLength
     */
    public static void parseRegisterMsg(Channel channel, ByteBuf in, int messageLength, int messageSeq) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < messageLength; i++) {
            builder.append((char) in.readByte());
        }
        String meterNumber = builder.toString().trim();
        log.info("meterNumber(终端编号): {}", meterNumber);

        //创建设备注册应答消息对象
        RegisterRespMsg registerRespMsg = new RegisterRespMsg();
        registerRespMsg.setMessageSeq(messageSeq);

        //检查终端编号是否有效
        boolean isValid = isMeterNumberValid(meterNumber);
        if (!isValid) {
            log.warn("注册失败！终端编号[{}]格式非法。", meterNumber);
            registerRespMsg.setRegResult((short) 2);
        } else {
            //检查终端编号是否已重复
            IMeterInfoService meterInfoService = SpringUtils.getBean(IMeterInfoService.class);
            MeterInfo existMeterInfo = meterInfoService.selectMeterInfoByMeterNumber(meterNumber);
            if (Objects.nonNull(existMeterInfo)) { //对应终端编号的设备在后台已注册
                log.warn("注册失败！终端编号[{}]已被注册。", meterNumber);
                registerRespMsg.setRegResult((short) 1);
            } else { //对应终端编号的设备在后台还未注册，可以注册
                MeterInfo newMeterInfo = new MeterInfo();
                newMeterInfo.setRegisterInfo("注册成功");
                newMeterInfo.setRegisterStatus(2);

                //查询所有终端信息对象
                List<MeterInfo> meterInfos = meterInfoService.selectMeterInfoList(new MeterInfo());
                //生成发动机编号。格式示例：E100007
//                String engineNumber = generateEngineNumber(meterInfos);
                newMeterInfo.setMeterNumber(meterNumber);
                int num = meterInfoService.insertMeterInfo(newMeterInfo);
                if (num > 0) {
                    int meterId = newMeterInfo.getMeterId();
                    //生成发动机编号。格式示例：E100007
                    String engineNumber = "E" + meterId;
                    newMeterInfo.setEngineNumber(engineNumber);
                    newMeterInfo.setShipNumber(engineNumber);
                    meterInfoService.updateMeterInfo(newMeterInfo);

                    log.info("注册成功！终端编号：{}, 终端信息：{}。", meterNumber, newMeterInfo);
                    //设置注册结果为注册成功。0：成功；1：仪表已注册；2：注册失败
                    registerRespMsg.setRegResult((short) 0);
                    registerRespMsg.setMeterId(meterId);
                    registerRespMsg.setEngineNumber(engineNumber);
                    registerRespMsg.setShipNumber(engineNumber);
                } else {
                    log.warn("注册失败！终端信息[meterNumber={}]插入数据库异常!", meterNumber);
                    registerRespMsg.setRegResult((short) 1);
                }
            }
        }

        channel.writeAndFlush(registerRespMsg);
    }

    /**
     * 生成发动机编号。格式示例：WJ0000000001
     *
     * @param size
     * @return
     */
//    private static String generateEngineNumber(long size) {
//        String s = String.valueOf(size);
//        int length = s.length();
//        if (length > 10) return IdUtil.simpleUUID();
//
//        StringBuilder builder = new StringBuilder("E");
//        for (int i = 0; i < (10 - length); i++) {
//            builder.append("0");
//        }
//        builder.append(s);
//        return builder.toString();
//    }

    /**
     * 生成发动机编号。格式示例：E100007
     *
     * @param meterInfos
     * @return
     */
    private static String generateEngineNumber(List<MeterInfo> meterInfos) {
        MeterInfo lastMeterInfo = meterInfos.get(meterInfos.size() - 1);
        String meterIdStr = lastMeterInfo.getMeterId().toString();
        return "E" + meterIdStr;
    }

    /**
     * 检查终端编号是否有效。正确格式示例：WJ24010001，24代表年份，01代表第几周，最后的4位数字为随机数字。
     *
     * @param meterNumber
     * @return
     */
    private static boolean isMeterNumberValid(String meterNumber) {
        if (StringUtils.isEmpty(meterNumber)) {
            return false;
        }
        //检查终端编号长度是否为10
//        if (meterNumber.length() != 10) {
//            return false;
//        }
        //检查终端编号是否以WJ开头
//        if (!meterNumber.startsWith("WJ")) {
//            return false;
//        }

        return true;
    }

    /**
     * 给设备端发送消息。
     */
    public static void sendMsgToClient() {

    }

    public static void parseAuthMsg(Channel channel, ByteBuf in, int messageLength) {
        StringBuilder engineNumberBuilder = new StringBuilder();
        StringBuilder shipNumberBuilder = new StringBuilder();
        int count = 0;
        byte item = -128;
        for (int i = 0; i < messageLength; i++) {
            item = in.readByte();
            if (count == 0) {
                if (item != 0) {
                    engineNumberBuilder.append((char) item);
                } else {
                    count++;
                }
            } else {
                if (item != 0) {
                    shipNumberBuilder.append((char) item);
                }
            }
        }
        String engineNumber = engineNumberBuilder.toString();
        String shipNumber = shipNumberBuilder.toString();
        log.info("engineNumber: {}, shipNumber: {}", engineNumber, shipNumber);

        IMeterInfoService meterInfoService = SpringUtils.getBean(IMeterInfoService.class);
        MeterInfo meterInfo = new MeterInfo();
        meterInfo.setEngineNumber(engineNumber);
        meterInfo.setShipNumber(shipNumber);
        List<MeterInfo> meterInfos = meterInfoService.selectMeterInfoList(meterInfo);

        AuthRespMsg authRespMsg = new AuthRespMsg();
        authRespMsg.setCurrTime(new Date());
        if (Objects.nonNull(meterInfos) && meterInfos.size() > 0) {
            authRespMsg.setAuthResult((byte) 0); //设置返回结果为“成功”
            log.info("鉴权成功！匹配的终端信息：{}。", meterInfos);
        } else {
            //设置返回结果为“失败”。1：发动机编号不对；
            authRespMsg.setAuthResult((byte) 1);
            log.warn("鉴权失败！未找到匹配的终端信息[engineNumber: {}, shipNumber: {}]。", engineNumber, shipNumber);
        }
        channel.writeAndFlush(authRespMsg);
    }

    /**
     * 从心跳报文原始数据中解析出指定类型的数据。
     *
     * @param meterId    终端ID
     * @param msgContent 心跳报文原始数据。
     * @param infoType   要获取的状态信息类型。
     *                   * 1：位置信息。
     *                   * 2：船机工况信息。
     *                   * 3：发动机故障信息。
     *                   * 4：报警信息。
     *                   * 5：当前驾驶循环统计信息。
     *                   * 6：配置心跳信息-J1939。
     *                   * 7：锁车相关信息。
     * @return
     */
    public static BoatStatusInfo getBoatStatusInfoFromHeartbeatMsgContent(long meterId, String msgContent, int infoType) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(256);
        buffer.writeBytes(HexUtil.decodeHex(msgContent));

        //跳过前面的12个字节(标识位+转义判断符+消息头共占12个字节)，直接开始读取和解析消息体
        buffer.skipBytes(12);

        //读取心跳报文生成的时间(注意: 非接收到报文的时间) 6个字节: BCD[6]
        Date generateTime = readTimeFromByteBuf(buffer);
        //获取当前心跳报文接收到的时间
        Date receiveTime = DateUtils.getNowDate();

        //解析心跳报文消息体
        //读取设备状态 2个字节
        buffer.skipBytes(2);
        //读取心跳报文中包含的信息包条数，即后续信息的条数。1个字节
        byte msgNum = buffer.readByte();
        //读取信息包
        for (byte i = 0; i < msgNum; i++) {
            //读取信息包ID 1个字节。
            byte msgId = buffer.readByte();
            //信息包长度
            int msgLength = 0;
            //信息 ID 为 0x02、0x06、0x08、0x09 时此字段为 2BYTE，其余ID为 1BYTE
            if (msgId == 0x02 || msgId == 0x06 || msgId == 0x08 || msgId == 0x09) {
                msgLength = buffer.readShort();
            } else {
                msgLength = buffer.readByte();
            }

            if (infoType != msgId) {
                buffer.skipBytes(msgLength);
            } else {
                if (infoType == 1) { //位置信息
                    return parseBoatLocationInfo(generateTime, meterId, msgLength, buffer);
                } else if (infoType == 2) { //船机工况信息
                    return parseBoatMachineWorkingInfo(generateTime, meterId, msgLength, buffer);
                } else if (infoType == 5) { //当前驾驶循环统计信息
                    return parseCurrentDrivingCycleStatisticsInfo(generateTime, meterId, msgLength, buffer);
                } else {
                    buffer.skipBytes(msgLength);
                }
            }
        }

        return null;
    }

    /**
     * 从心跳报文数据对象中解析出指定类型的数据。
     *
     * @param meterId      终端ID
     * @param heartbeatMsg 心跳报文数据对象
     * @param infoType     要获取的状态信息类型。
     *                     * 1：位置信息。
     *                     * 2：船机工况信息。
     *                     * 3：发动机故障信息。
     *                     * 4：报警信息。
     *                     * 5：当前驾驶循环统计信息。
     *                     * 6：配置心跳信息-J1939。
     *                     * 7：锁车相关信息。
     * @return
     */
    public static BoatStatusInfo getBoatStatusInfoFromHeartbeatMsg(long meterId, HeartbeatMsg heartbeatMsg, int infoType) {
        String msgContent = heartbeatMsg.getMsgContent();
        return getBoatStatusInfoFromHeartbeatMsgContent(meterId, msgContent, infoType);
    }

    /**
     * 解析船端发送过来的心跳报文。
     *
     * @param channel
     * @param in
     * @param meterId
     * @param messageId
     */
    public static void parseHeartbeatUploadMsg(Channel channel, ByteBuf in, long meterId, int messageId) {
//        log.info("in class: {}", in.getClass()); //输出：class io.netty.buffer.PooledUnsafeDirectByteBuf
//        log.info("<========打印合并前的心跳报文：===========>");
//        MyByteUtil.logByteBuf(in);
        in.resetReaderIndex();
        ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(1);
        buf1.writeByte(HexUtil.hexToInt("7e"));
        //in的首位需各加一个"7e"，即buf1
        ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(buf1, in, buf1);
        //将合并后的bytebuf转为16进制字符串，该字符串需要保存到数据库中，方便后续读取
        String heartbeatMsgContent = ByteBufUtil.hexDump(wrappedBuffer);

//        log.info("<========打印合并后的心跳报文：===========>");
//        MyByteUtil.logByteBuf(wrappedBuffer); //合并后的buf的readIndex为0.

        //跳过前面的12个字节(标识位+转义判断符+消息头共占12个字节)，直接开始读取和解析消息体
        wrappedBuffer.skipBytes(12);

        //读取心跳报文生成的时间(注意: 非接收到报文的时间) 6个字节: BCD[6]
        Date generateTime = readTimeFromByteBuf(wrappedBuffer);
        //获取当前心跳报文接收到的时间
        Date receiveTime = DateUtils.getNowDate();

        //判断设备ID是否为0,0即表示无设备ID
        if (meterId != 0) {
            IMeterInfoService meterInfoService = SpringUtils.getBean(IMeterInfoService.class);
            //根据设备ID获取系统中对应的终端信息
            MeterInfo meterInfo = meterInfoService.selectMeterInfoByMeterid(meterId);
            if (Objects.nonNull(meterInfo)) {
                String meterNumber = meterInfo.getMeterNumber();
                //读取终端最新开机时间
                BoatStatusInfo boatStatusInfo = getBoatStatusInfoFromHeartbeatMsgContent(meterId, heartbeatMsgContent, 5);
                if (Objects.nonNull(boatStatusInfo)) {
                    if (boatStatusInfo instanceof BoatCurrentDrivingCycleStatisticsInfo) {
                        BoatCurrentDrivingCycleStatisticsInfo cycleStatisticsInfo = (BoatCurrentDrivingCycleStatisticsInfo) boatStatusInfo;
                        Date drivingCycleBeginTime = cycleStatisticsInfo.getDrivingCycleBeginTime();
//                        log.info("drivingCycleBeginTime: {}", drivingCycleBeginTime);
                        meterInfo.setLastBootTime(drivingCycleBeginTime);
                    }
                }
                meterInfo.setLastTime(receiveTime);
                meterInfoService.updateMeterInfo(meterInfo);

                //将心跳原始报文保存到数据库中
                IHeartbeatMsgService heartbeatMsgService = SpringUtils.getBean(IHeartbeatMsgService.class);
                HeartbeatMsg heartbeatMsg = new HeartbeatMsg();
                heartbeatMsg.setMeterId(meterId);
                heartbeatMsg.setMsgContent(heartbeatMsgContent);
                heartbeatMsg.setMsgLength(heartbeatMsgContent.length() / 2);
                heartbeatMsg.setGenerateTime(generateTime);
                heartbeatMsg.setReceiveTime(receiveTime);
                int num = heartbeatMsgService.insertHeartbeatMsg(heartbeatMsg);
                if (num > 0) {
                    log.info("终端[meterId={},meterNumber={}]心跳数据上传成功!", messageId, meterNumber);
                }

                //解析心跳报文消息体
                //读取设备状态 2个字节
//                wrappedBuffer.skipBytes(2);
//                //读取心跳报文中包含的信息包条数，即后续信息的条数。1个字节
//                byte msgNum = wrappedBuffer.readByte();
//                //读取信息包
//                for (byte i = 0; i < msgNum; i++) {
//                    //读取信息包ID 1个字节。
//                    byte msgId = wrappedBuffer.readByte();
//                    //信息包长度
//                    int msgLength = 0;
//                    //信息 ID 为 0x02、0x06、0x08、0x09 时此字段为 2BYTE，其余ID为 1BYTE
//                    if (msgId == 0x02 || msgId == 0x06 || msgId == 0x08 || msgId == 0x09) {
//                        msgLength = wrappedBuffer.readShort();
//                    } else {
//                        msgLength = wrappedBuffer.readByte();
//                    }
//                    //读取和解析信息包数据
//                    parseMsgData(generateTime, meterId, msgId, msgLength, wrappedBuffer);
//                }

            }
        }

        //需自己手动释放wrappedBuffer
//        wrappedBuffer.release();

        //给设备发送平台通用应答消息
        sendCommonResp(channel, messageId);
    }

    /**
     * 解析船端发送过来的盲区心跳报文。
     *
     * @param channel
     * @param in
     * @param meterId
     * @param messageId
     */
    public static void parseHeartbeatReuploadMsg(Channel channel, ByteBuf in, long meterId, int messageId) {
        in.resetReaderIndex();
        ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(1);
        buf1.writeByte(HexUtil.hexToInt("7e"));
        //in的首位需各加一个"7e"，即buf1
        ByteBuf wrappedBuffer = Unpooled.wrappedBuffer(buf1, in, buf1);
        //将合并后的bytebuf转为16进制字符串，该字符串需要保存到数据库中，方便后续读取
        String heartbeatMsgContent = ByteBufUtil.hexDump(wrappedBuffer);

        //跳过前面的12个字节(标识位+转义判断符+消息头共占12个字节)，直接开始读取和解析消息体
        wrappedBuffer.skipBytes(12);

        //读取心跳报文生成的时间(注意: 非接收到报文的时间) 6个字节: BCD[6]
        Date generateTime = readTimeFromByteBuf(wrappedBuffer);
        //获取当前心跳报文接收到的时间
        Date receiveTime = DateUtils.getNowDate();

        //判断设备ID是否为0,0即表示无设备ID
        if (meterId != 0) {
            IMeterInfoService meterInfoService = SpringUtils.getBean(IMeterInfoService.class);
            //根据设备ID获取系统中对应的终端信息
            MeterInfo meterInfo = meterInfoService.selectMeterInfoByMeterid(meterId);
            if (Objects.nonNull(meterInfo)) {
                String meterNumber = meterInfo.getMeterNumber();
                meterInfo.setLastTime(receiveTime);
                meterInfoService.updateMeterInfo(meterInfo);

                //将心跳原始报文保存到数据库中
                IHeartbeatMsgService heartbeatMsgService = SpringUtils.getBean(IHeartbeatMsgService.class);
                HeartbeatMsg heartbeatMsg = new HeartbeatMsg();
                heartbeatMsg.setMeterId(meterId);
                heartbeatMsg.setMsgContent(heartbeatMsgContent);
                heartbeatMsg.setMsgLength(heartbeatMsgContent.length() / 2);
                heartbeatMsg.setGenerateTime(generateTime);
                heartbeatMsg.setReceiveTime(receiveTime);
                int num = heartbeatMsgService.insertHeartbeatMsg(heartbeatMsg);
                if (num > 0) {
                    log.info("终端[meterId={},meterNumber={}]盲区心跳数据上传成功!", messageId, meterNumber);
                }
            }
        }

        //给设备发送平台通用应答消息
        sendCommonResp(channel, messageId);
    }

    /**
     * 解析信息包数据。
     *
     * @param generateTime  报文的生成时间，非接收时间。
     * @param meterId       设备ID
     * @param msgId         信息ID
     *                      0x01：位置信息
     *                      0x02：船机工况信息
     *                      ...
     * @param msgLength     信息包数据长度
     * @param wrappedBuffer
     */
    private static void parseMsgData(Date generateTime, long meterId, byte msgId, int msgLength, ByteBuf wrappedBuffer) {
//        if (msgId == 0x01) { //船机位置信息
//            parseBoatLocationInfo(generateTime, meterId, msgLength, wrappedBuffer);
//        } else if (msgId == 0x02) { //船机工况信息
//            parseBoatMachineWorkingInfo(generateTime, meterId, msgLength, wrappedBuffer);
//        } else {
//            wrappedBuffer.skipBytes(msgLength);
//        }
    }

    /**
     * 解析船舶位置信息。
     *
     * @param meterId
     * @param msgLength
     * @param wrappedBuffer
     */
    private static BoatLocationInfo parseBoatLocationInfo(Date generateTime, long meterId, int msgLength, ByteBuf wrappedBuffer) {
        byte byte1 = wrappedBuffer.readByte();
        //创建船舶位置信息对象
        BoatLocationInfo boatLocationInfo = new BoatLocationInfo();

        //读取定位状态，1个字节 bit0 = 0：定位；bit0 = 1：不定位；
        byte locationStatus = MyByteUtil.getBitValueFromByte(byte1, 7);
        boatLocationInfo.setLocationStatus(locationStatus);
        boatLocationInfo.setMeterId(meterId);
        boatLocationInfo.setCreateTime(generateTime);
        if (locationStatus == 0) { //定位
            //读取经度方位，bit1 = 0：东经；bit1 = 1：西经；
            byte longitudeOri = MyByteUtil.getBitValueFromByte(byte1, 6);
            //读取纬度方位，bit2 = 0：南纬；bit2 = 1：北纬；
            byte latitudeOri = MyByteUtil.getBitValueFromByte(byte1, 5);

            String latitude = "unknown";
            try {
                //解析纬度。占4个字节。
                latitude = parseLatitude(latitudeOri, wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            String longitude = "unknown";
            try {
                //解析经度。占5个字节。
                longitude = parseLongitude(longitudeOri, wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            BigDecimal elevation = new BigDecimal(0).setScale(2, RoundingMode.DOWN);
            try {
                //解析高程。占4个字节。
                elevation = parseElevation(wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            BigDecimal speed = new BigDecimal(0).setScale(1, RoundingMode.DOWN);
            try {
                //解析速度。占3个字节。
                speed = parseSpeed(wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            BigDecimal orientation = new BigDecimal(0).setScale(1, RoundingMode.DOWN);
            try {
                //解析速度。占3个字节。
                orientation = parseOrientation(wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            //读取卫星个数 1个字节
            short satellitesNum = wrappedBuffer.readUnsignedByte();

            BigDecimal hdop = new BigDecimal(0).setScale(2, RoundingMode.DOWN);
            try {
                //解析水平分量精度因子。占2个字节。
                hdop = parseHdop(wrappedBuffer);
            } catch (Exception e) {
                log.warn(e.getMessage());
            }

            //读取GSM信号强度 1个字节
            short csq = wrappedBuffer.readUnsignedByte();

            boatLocationInfo.setLongitudeOri(longitudeOri);
            boatLocationInfo.setLatitudeOri(latitudeOri);
            boatLocationInfo.setLatitude(latitude);
            boatLocationInfo.setLongitude(longitude);
            boatLocationInfo.setElevation(elevation);
            boatLocationInfo.setSpeed(speed);
            boatLocationInfo.setOrientation(orientation);
            boatLocationInfo.setSatellitesNum(satellitesNum);
            boatLocationInfo.setHdop(hdop);
            boatLocationInfo.setCsq(csq);
        } else { //不定位
            //不定位情况下其它剩余字节直接跳过，无需解析
            wrappedBuffer.skipBytes(msgLength - 1);
        }

//        IBoatLocationInfoService boatLocationInfoService = SpringUtils.getBean(IBoatLocationInfoService.class);
//        boatLocationInfoService.insertBoatLocationInfo(boatLocationInfo);

        return boatLocationInfo;
    }

    /**
     * 解析水平分量精度因子。占2个字节。解析示例：99 99 => 99.99
     *
     * @param
     * @return
     * @author ZJW
     * @createTime 2024/1/24 23:49
     */
    public static BigDecimal parseHdop(ByteBuf wrappedBuffer) {
        //读取水平分量精度因子 2个字节组成
        String s1 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String s2 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());

        StringBuilder hdopStr = new StringBuilder();
        BigDecimal hdop = null;
        try {
            String tempStr1 = String.valueOf(Integer.parseInt(s1));
            if (tempStr1.equals("0")) {
                hdopStr.append(".");
                hdopStr.append(s2);
            } else {
                hdopStr.append(tempStr1);
                hdopStr.append(".");
                hdopStr.append(s2);
            }

            //hdop = new BigDecimal(hdopStr.toString()).setScale(2, RoundingMode.HALF_UP); //RoundingMode.HALF_UP 四舍五入
            hdop = new BigDecimal(hdopStr.toString()).setScale(2, RoundingMode.DOWN);
        } catch (Exception e) {
            String originMsg = s1 + " " + s2;
            throw new RuntimeException(String.format("水平分量精度因子数据解析失败, 水平分量精度因子数据原始报文：%s", originMsg));
        }

        log.info("hdop: {}", hdop);

        return hdop;
    }

    /**
     * 解析方向。占3个字节。解析示例：02 70 10 => 270.1
     *
     * @param
     * @return
     * @author ZJW
     * @createTime 2024/1/24 23:38
     */
    public static BigDecimal parseOrientation(ByteBuf wrappedBuffer) {
        String s1 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String s2 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String s3 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());

        StringBuilder orientationStr = new StringBuilder();
        BigDecimal orientation = null;
        try {
            String tempStr1 = String.valueOf(Integer.parseInt(s1));
            String tempStr2 = String.valueOf(Integer.parseInt(s2));
            if (tempStr1.equals("0")) {
                if (tempStr2.equals("0")) {
                    orientationStr.append("0." + s3);
                } else {
                    orientationStr.append(tempStr2);
                    orientationStr.append(".");
                    orientationStr.append(s3);
                }
            } else {
                orientationStr.append(tempStr1);
                orientationStr.append(s2);
                orientationStr.append(".");
                orientationStr.append(s3);
            }
            orientation = new BigDecimal(orientationStr.toString()).setScale(1, RoundingMode.DOWN);
        } catch (Exception e) {
            String originMsg = s1 + " " + s2 + " " + s3;
            throw new RuntimeException(String.format("方向数据解析失败, 方向数据原始报文：%s", originMsg));
        }
//        log.info("orientation: {}", orientation);

        return orientation;
    }

    /**
     * 解析速度。占3个字节。解析示例：01 10 50 => 110.5
     *
     * @param
     * @return
     * @author ZJW
     * @createTime 2024/1/24 23:18
     */
    public static BigDecimal parseSpeed(ByteBuf wrappedBuffer) {
        String s1 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String s2 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String s3 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());

        StringBuilder speedStr = new StringBuilder();
        BigDecimal speed = null;
        try {
            String tempStr1 = String.valueOf(Integer.parseInt(s1));
            String tempStr2 = String.valueOf(Integer.parseInt(s2));
            if (tempStr1.equals("0")) {
                if (tempStr2.equals("0")) {
                    speedStr.append("0." + s3);
                } else {
                    speedStr.append(tempStr2);
                    speedStr.append(".");
                    speedStr.append(s3);
                }
            } else {
                speedStr.append(tempStr1);
                speedStr.append(s2);
                speedStr.append(".");
                speedStr.append(s3);
            }
            speed = new BigDecimal(speedStr.toString()).setScale(1, RoundingMode.DOWN);
        } catch (Exception e) {
            String originMsg = s1 + " " + s2 + " " + s3;
            throw new RuntimeException(String.format("速度数据解析失败, 速度数据原始报文：%s", originMsg));
        }

//        log.info("speed: {}", speed);

        return speed;
    }

    /**
     * 解析高程。占4个字节。解析示例：2d 12 38 31 => -1238.31，00 12 38 31 => 1238.31
     *
     * @param wrappedBuffer
     * @return
     */
    public static BigDecimal parseElevation(ByteBuf wrappedBuffer) {
//        byte h1 = wrappedBuffer.readByte();
        String h1 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String h2 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String h3 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());
        String h4 = StringUtil.byteToHexStringPadded(wrappedBuffer.readUnsignedByte());

        StringBuilder elevationStr = new StringBuilder();
        if (h1.equalsIgnoreCase("2D")) { //负数
            elevationStr.append('-');
        }
        BigDecimal elevation = null;
        try {
            String tempStr1 = String.valueOf(Integer.parseInt(h2));
            String tempStr2 = String.valueOf(Integer.parseInt(h3));
            if (tempStr1.equals("0")) {
                if (tempStr2.equals("0")) {
                    elevationStr.append("0." + h4);
                } else {
                    elevationStr.append(tempStr2);
                    elevationStr.append(".");
                    elevationStr.append(h4);
                }
            } else {
                elevationStr.append(tempStr1);
                elevationStr.append(h3);
                elevationStr.append(".");
                elevationStr.append(h4);
            }
            elevation = new BigDecimal(elevationStr.toString()).setScale(2, RoundingMode.DOWN);
        } catch (Exception e) {
            String originMsg = h1 + " " + h2 + " " + h3 + " " + h4;
            throw new RuntimeException(String.format("高程数据解析失败, 高程数据原始报文：%s", originMsg));
        }
//        log.info("elevation: {}", elevation);

        return elevation;
    }

    /**
     * 解析纬度。占4个字节。
     * 解析结果示例：输出结果示例：32 04 64 55 => 32°4′38.73″N
     *
     * @param latitudeOri 纬度方位，0：南纬，1：北纬；
     * @param byteBuf
     * @return
     * @throws NumberFormatException
     */
    public static String parseLatitude(byte latitudeOri, ByteBuf byteBuf) {
        //读取纬度，4个字节组成
        String lat1 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lat2 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lat3 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lat4 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());

        String latitude = "";
        try {
            String degreeStr = String.valueOf(Integer.parseInt(lat1));
            String minuteStr = String.valueOf(Integer.parseInt(lat2));
            String tempStr = "0." + lat3 + lat4;
            BigDecimal bigDecimal = new BigDecimal(Double.parseDouble(tempStr) * 60).setScale(2, RoundingMode.DOWN);
            String secondStr = bigDecimal.toString();

            latitude = degreeStr + "°" + minuteStr + "′" + secondStr + "″";
            //bit2 = 0：南纬；bit2 = 1：北纬；
            if (latitudeOri == 0) {
                latitude = latitude + "S";
            } else if (latitudeOri == 1) {
                latitude = latitude + "N";
            }
        } catch (Exception e) {
            String originMsg = lat1 + " " + lat2 + " " + lat3 + " " + lat4;
            throw new RuntimeException(String.format("纬度数据解析失败, 纬度数据原始报文：%s", originMsg));
        }
//        log.info("latitude: {}", latitude);

        return latitude;
    }

    /**
     * 解析经度。占5个字节。
     * 解析结果示例：输出结果示例：01 20 29 94 42 => 120°29′56.65″E
     *
     * @param longitudeOri 经度方位，0：东经，1：西经；
     * @param byteBuf
     * @return
     * @throws NumberFormatException
     */
    public static String parseLongitude(byte longitudeOri, ByteBuf byteBuf) {
        String lon1 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lon2 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lon3 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lon4 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());
        String lon5 = StringUtil.byteToHexStringPadded(byteBuf.readUnsignedByte());

        String longitude = "";
        try {
            String degreeStr = Integer.parseInt(lon1) + lon2;
            String minuteStr = String.valueOf(Integer.parseInt(lon3));
            String tempStr = "0." + lon4 + lon5;
            BigDecimal bigDecimal = new BigDecimal(Double.parseDouble(tempStr) * 60).setScale(2, RoundingMode.DOWN);
            String secondStr = bigDecimal.toString();

            longitude = degreeStr + "°" + minuteStr + "′" + secondStr + "″";
            //判断经度方位，0：东经，1：西经；
            if (longitudeOri == 0) {
                longitude = longitude + "E";
            } else if (longitudeOri == 1) {
                longitude = longitude + "W";
            }
        } catch (NumberFormatException e) {
            String originMsg = lon1 + " " + lon2 + " " + lon3 + " " + lon4 + " " + lon5;
            throw new RuntimeException(String.format("经度数据解析失败, 经度数据原始报文：%s", originMsg));
        }
//        log.info("longitude: {}", longitude);

        return longitude;
    }

    /**
     * 解析船舶工况信息。
     *
     * @param meterId
     * @param msgLength
     * @param wrappedBuffer
     */
    private static BoatMachineWorkingInfo parseBoatMachineWorkingInfo(Date generateTime, long meterId, int msgLength, ByteBuf wrappedBuffer) {
        BoatMachineWorkingInfo workingInfo = new BoatMachineWorkingInfo();

        //读取船舶机型ID 2个字节
        int boatMachineModelId = wrappedBuffer.readShort();
        //wrappedBuffer.skipBytes(2);
        //读取船舶机型工况信息
        if (boatMachineModelId == 0x0001) { //本埠电控直列柴油机
            //读取转速 2个字节
            int speed = wrappedBuffer.readUnsignedShort();
            //读取扭矩 4个字节
            long torque = wrappedBuffer.readUnsignedInt();
            //读取负荷率 1个字节
            short loadRate = wrappedBuffer.readUnsignedByte();
            //读取油门开度 1个字节
            short throttleOpening = wrappedBuffer.readUnsignedByte();
            //读取瞬时油耗 2个字节
            int instantFuelConsumption = wrappedBuffer.readUnsignedShort();
            //读取累计油耗 4个字节 (14)
            long accumulateFuelConsumption = wrappedBuffer.readUnsignedInt();

            //读取发动机运行时间 4个字节
            long engineOperateTime = wrappedBuffer.readUnsignedInt();
            //读取机油温度 2个字节
            int engineOilTemp = wrappedBuffer.readUnsignedShort();
            //读取机油压力 1个字节
            short engineOilPressure = wrappedBuffer.readUnsignedByte();

            //读取出水温度 1个字节 (已核对)
            short outWaterTemp = wrappedBuffer.readUnsignedByte();

            //读取排气温度 2个字节 (未核对)
            int exhaustGasTemp = wrappedBuffer.readUnsignedShort();

            //读取进气压力 1个字节 (已核对)
            short inGasPressure = wrappedBuffer.readUnsignedByte();
            //读取进气温度 1个字节 (已核对)
            short inGasTemp = wrappedBuffer.readUnsignedByte();
            //读取蓄电池电压 2个字节 (已核对)
            int storageBatteryVoltage = wrappedBuffer.readUnsignedShort();
            //读取燃油温度 1个字节
            short fuelOilTemp = wrappedBuffer.readUnsignedByte();
            //读取燃油精滤后压力 1个字节
            short fuelOilAfterFilteredPressure = wrappedBuffer.readUnsignedByte();
            //读取燃油精滤前压力 1个字节
            short fuelOilBeforeFilteredPressure = wrappedBuffer.readUnsignedByte();
            //读取设定轨压 2个字节
            int settingTrackPressure = wrappedBuffer.readUnsignedShort();
            //读取实际轨压 2个字节
            int reallyTrackPressure = wrappedBuffer.readUnsignedShort();
            //读取燃油负压 1个字节
            short fuelOilNegativePressure = wrappedBuffer.readUnsignedByte();
            //读取曲轴箱压力 1个字节
            short crankcasePressure = wrappedBuffer.readUnsignedByte();
            //读取机油滤前压力 1个字节
            short engineOilBeforeFilteredPressure = wrappedBuffer.readUnsignedByte();

            //跳过剩余字节数
            wrappedBuffer.skipBytes(msgLength - 2 - 38);

            workingInfo.setCreateTime(generateTime);
            workingInfo.setMeterId(meterId);
            workingInfo.setMachineModelId(boatMachineModelId);
            workingInfo.setSpeed((int) (speed * 0.125));
            workingInfo.setTorque(torque);
            workingInfo.setLoadRate(loadRate);
            workingInfo.setThrottleOpening(new BigDecimal(throttleOpening * 0.4).setScale(1, RoundingMode.DOWN));
//                workingInfo.setInstantFuelConsumption((int) (instantFuelConsumption * 0.05));
            workingInfo.setInstantFuelConsumption(new BigDecimal(instantFuelConsumption * 0.05).setScale(1, RoundingMode.DOWN));
            workingInfo.setAccumulateFuelConsumption((long) (accumulateFuelConsumption * 0.5));

            workingInfo.setEngineOperateTime(new BigDecimal(engineOperateTime * 0.05).setScale(1, RoundingMode.DOWN));
            workingInfo.setEngineOilTemp((int) (engineOilTemp * 0.03125) - 273);
//            workingInfo.setEngineOilPressure(engineOilPressure);
            workingInfo.setEngineOilPressure(new BigDecimal(engineOilPressure * 4 * 0.001).setScale(2, RoundingMode.DOWN));
            workingInfo.setOutWaterTemp((short) (outWaterTemp - 40));
            workingInfo.setExhaustGasTemp((int) (exhaustGasTemp * 0.125) - 273);
            workingInfo.setInGasPressure((short) (inGasPressure * 2));
            workingInfo.setInGasTemp((short) (inGasTemp - 40));
            workingInfo.setStorageBatteryVoltage(new BigDecimal(storageBatteryVoltage * 0.05).setScale(1, RoundingMode.DOWN));
            workingInfo.setFuelOilTemp((short) (fuelOilTemp - 40));
            //kPa换算为MPa需乘以0.001
            workingInfo.setFuelOilAfterFilteredPressure(new BigDecimal(fuelOilAfterFilteredPressure * 4 * 0.001).setScale(2, RoundingMode.DOWN));
            workingInfo.setFuelOilBeforeFilteredPressure(new BigDecimal(fuelOilBeforeFilteredPressure * 4 * 0.001).setScale(2, RoundingMode.DOWN));
            workingInfo.setSettingTrackPressure(settingTrackPressure * 100);
            workingInfo.setReallyTrackPressure(reallyTrackPressure * 100);
//                workingInfo.setFuelOilNegativePressure(fuelOilNegativePressure);
            workingInfo.setFuelOilNegativePressure(new BigDecimal(fuelOilNegativePressure - 125).setScale(2, RoundingMode.DOWN));
            workingInfo.setCrankcasePressure((short) (crankcasePressure * 0.4 - 50));
//                workingInfo.setEngineOilBeforeFilteredPressure(engineOilBeforeFilteredPressure);
            workingInfo.setEngineOilBeforeFilteredPressure(new BigDecimal(engineOilBeforeFilteredPressure * 4 * 0.001).setScale(2, RoundingMode.DOWN));

//            IBoatMachineWorkingInfoService machineWorkingInfoService = SpringUtils.getBean(IBoatMachineWorkingInfoService.class);
//            machineWorkingInfoService.insertBoatMachineWorkingInfo(workingInfo);

        } else {
            wrappedBuffer.skipBytes(msgLength - 2);
        }

        return workingInfo;
    }

    private static BoatStatusInfo parseCurrentDrivingCycleStatisticsInfo(Date generateTime, long meterId, int msgLength, ByteBuf buffer) {
        BoatCurrentDrivingCycleStatisticsInfo cycleStatisticsInfo = new BoatCurrentDrivingCycleStatisticsInfo();
        //读取驾驶循环开始时间。6个字节
        Date drivingCycleBeginTime = readTimeFromByteBuf(buffer);

        //跳过后面13个用不上的字节信息
        buffer.skipBytes(13);

        cycleStatisticsInfo.setCreateTime(generateTime);
        cycleStatisticsInfo.setMeterId(meterId);
        cycleStatisticsInfo.setDrivingCycleBeginTime(drivingCycleBeginTime);

        return cycleStatisticsInfo;
    }

    /**
     * 给设备发送平台通用应答消息。
     */
    public static void sendCommonResp(Channel channel, int deiceMessageId) {
        CommonRespMsg commonRespMsg = new CommonRespMsg();
        commonRespMsg.setMessageId(deiceMessageId);
        commonRespMsg.setRespResult((byte) 0); //设置结果为成功。

//        log.info("给客户端回复心跳报文！");
        channel.writeAndFlush(commonRespMsg);
    }

    private static ByteBuf createHeader(int bodyLength, Message respMsg) {
        ByteBuf header = ByteBufAllocator.DEFAULT.buffer();
        //2.1 写入消息id
        header.writeShort(respMsg.getMessageId());
        //2.2 构建并写入消息体属性
        int messageProp = 0;
        //2.2.1 将消息体长度写入消息体属性中的0~10位

        messageProp = MyByteUtil.writeValueToAssignBit(bodyLength, messageProp, 0, 10);
        //2.2.2 将数据加密方式写入消息体属性中的11~13位 (数据加密方式默认值为0，即不加密)
        messageProp = MyByteUtil.writeValueToAssignBit(0, messageProp, 11, 13);
        //2.2.3 将是否分包标识符写入消息体属性中的14位 (是否分包标识符默认值为0，即不分包)
        messageProp = MyByteUtil.writeValueToAssignBit(0, messageProp, 14, 14);
        //2.2.4 写入保留字符
        messageProp = MyByteUtil.writeValueToAssignBit(0, messageProp, 15, 15);
        header.writeShort(messageProp);
        //2.3 写入设备id
        header.writeInt(CommonContants.Device_Id);
        //2.4 写入消息流水号
//            header.writeShort(MessageSeq.getAndIncrement());
        header.writeShort(respMsg.getMessageSeq());
        //2.5 写入消息包封装项(默认不分包，不分包则不要写入，所以默认不写入)

        return header;
    }

    public static ByteBuf encodeRegisterMsg(RegisterRespMsg registerRespMsg) {
        //1,构建消息体
        ByteBuf body = ByteBufAllocator.DEFAULT.buffer();
        //1.1 写入注册结果。0：成功；1：仪表已注册；2：注册失败
        short regResult = registerRespMsg.getRegResult();
        body.writeByte(regResult);
        if (regResult == 0) { //只有注册成功时才需写入设备id，发动机编号，左机编号
            //1.2 写入设备编号。
            body.writeInt(registerRespMsg.getMeterId());
            //1.3 写入发动机编号。
            String engineNumber = registerRespMsg.getEngineNumber();
            body.writeBytes(engineNumber.getBytes());
            body.writeByte(0x00); //结尾符
            //1.4 写入左机编号。
            String shipNumber = registerRespMsg.getShipNumber();
            body.writeBytes(shipNumber.getBytes());
            body.writeByte(0x00); //结尾符
        }

        //2,构建消息头
        int bodyLength = body.writerIndex();
        ByteBuf header = createHeader(bodyLength, registerRespMsg);

        //合并header与body
        ByteBuf mergeBuf = Unpooled.wrappedBuffer(header, body);

        return mergeBuf;
    }

    public static ByteBuf encodeAuthRespMsg(AuthRespMsg authRespMsg) {
        //1,构建消息体
        ByteBuf body = ByteBufAllocator.DEFAULT.buffer();
        //1.1 写入鉴权结果。0：成功；1：发动机编号不对；
        byte authResult = authRespMsg.getAuthResult();
        body.writeByte(authResult);

        //1.2 写入当前系统日期时间。
        Date date = new Date();
        int year = date.getYear() - 100;
        int month = date.getMonth() + 1;
        int currDate = date.getDate();
        int hour = date.getHours();
        int minute = date.getMinutes();
        int second = date.getSeconds();
        //如下测试结果示例：年:23,月：10,日：30,时：16,分：36，秒：14
//        log.info("年:{},月：{},日：{},时：{},分：{}，秒：{}", date.getYear() - 100, date.getMonth() + 1, date.getDate(),
//        date.getHours(), date.getMinutes(), date.getSeconds());
        body.writeByte(year);
        body.writeByte(month);
        body.writeByte(currDate);
        body.writeByte(hour);
        body.writeByte(minute);
        body.writeByte(second);

        //2,构建消息头
        int bodyLength = body.writerIndex();
        ByteBuf header = createHeader(bodyLength, authRespMsg);

        //合并header与body
        ByteBuf mergeBuf = Unpooled.wrappedBuffer(header, body);

        return mergeBuf;
    }

    public static ByteBuf encodeCommonRespMsg(CommonRespMsg commonRespMsg) {
        //1,构建消息体
        ByteBuf body = ByteBufAllocator.DEFAULT.buffer();
        //1.1 写入应答id。
        body.writeShort(commonRespMsg.getRespId());
        //1.2 写入应答结果。
        body.writeByte(0);

        //2,构建消息头
        int bodyLength = body.writerIndex();
        ByteBuf header = createHeader(bodyLength, commonRespMsg);

        //合并header与body
        ByteBuf mergeBuf = Unpooled.wrappedBuffer(header, body);

        return mergeBuf;
    }

    /**
     * 将bcd格式字节转成十进制数字。
     * 在BCD格式中，一个字节的高四位表示十位数字，低四位表示个位数字。例如，数字 12 在 BCD 格式中表示为0001 0010。
     * 关于BCD格式时间参考资料：https://blog.51cto.com/u_16213460/7229549
     *
     * @param bcd
     * @return
     */
    public static int bcdToDecimal(byte bcd) {
        return ((bcd >> 4) & 0x0F) * 10 + (bcd & 0x0F);
    }

    /**
     * 读取日期时间数据。6个字节: BCD(6)。
     *
     * @param in
     * @return
     */
    private static Date readTimeFromByteBuf(ByteBuf in) {
        DateTime result = DateUtil.parseDateTime("2000-01-01 08:00:00");
        int year = bcdToDecimal(in.readByte());
        int month = bcdToDecimal(in.readByte());
        int day = bcdToDecimal(in.readByte());
        int hour = bcdToDecimal(in.readByte());
        int minute = bcdToDecimal(in.readByte());
        int second = bcdToDecimal(in.readByte());
        Object[] timeArr = {year, month, day, hour, minute, second};
//        log.info("heartbeatMsg time: {}-{}-{}-{}-{}-{}", timeArr);
        try {
            result = DateUtil.parseDateTime(String.format("%d-%d-%d %d:%d:%d", year + 2000, month, day, hour, minute, second));
        } catch (Exception e) {
//            throw new RuntimeException(e);
            log.warn("报文时间解析出错，e: {}", e.getMessage(), e);
        }

        return result;
    }
}
